/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package okhttp3.internal.connection;

import java.io.IOException;
import java.lang.ref.Reference;
import java.net.Proxy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import okhttp3.Address;
import okhttp3.Route;
import okhttp3.internal.Util;
import okhttp3.internal.connection.Transmitter.TransmitterReference;
import okhttp3.internal.platform.Platform;

import static okhttp3.internal.Util.closeQuietly;

public final class RealConnectionPool {
  /**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   * 维护线程池，默认平常闲置不启动线程，条件符合后开启不限制数量的线程池，保持60秒的存活时间
   */
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  private final int maxIdleConnections;
  private final long keepAliveDurationNs;
  //制定一个 清理过期连接的 runnable;使用线程池进行调度使用，该runnable支持用线程池开启多个任务
  private final Runnable cleanupRunnable = () -> {
    while (true) {
      long waitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;//无闲置或者使用的，则无需继续执行
      if (waitNanos > 0) {//大于0则去阻塞等待下一次的执行时间
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L);
        synchronized (RealConnectionPool.this) {
          try {
            RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
          } catch (InterruptedException ignored) {
          }
        }
      }
    }
  };
  //真实的连接池 ；此连接非我说理解的，不同 ip所持有的不同的socket的连接，而是指http层每个url，对应的相关信息
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;

  public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

  public synchronized int idleConnectionCount() {
    int total = 0;
    for (RealConnection connection : connections) {
      if (connection.transmitters.isEmpty()) total++;
    }
    return total;
  }

  public synchronized int connectionCount() {
    return connections.size();
  }

  /**
   * Attempts to acquire a recycled connection to {@code address} for {@code transmitter}. Returns
   * true if a connection was acquired.
   *
   * <p>If {@code routes} is non-null these are the resolved routes (ie. IP addresses) for the
   * connection. This is used to coalesce related domains to the same HTTP/2 connection, such as
   * {@code square.com} and {@code square.ca}.//合并有关联的域名的connection
   */
  boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
      @Nullable List<Route> routes, boolean requireMultiplexed) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
    	//判断是否需要 多路复用 http2的connection
      if (requireMultiplexed && !connection.isMultiplexed()) continue;
      //如果 true 当前connection能够被当前 call用的地址所利用
      if (!connection.isEligible(address, routes)) continue;//取反跳过
      //获取连接没有事件，利用引用与connection进行关联
      transmitter.acquireConnectionNoEvents(connection);
      return true;
    }
    return false;
  }

  void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {//没有执行 清除runnable就开启该线程
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

  /**
   * Notify this pool that {@code connection} has become idle. Returns true if the connection has
   * been removed from the pool and should be closed.
   */
  boolean connectionBecameIdle(RealConnection connection) {
    assert (Thread.holdsLock(this));
    //connection为初始状态，或者最大闲置的 连接数设置为0，则直接移除connection
    if (connection.noNewExchanges || maxIdleConnections == 0) {
      connections.remove(connection);
      return true;
    } else {//不符合条件，通知清理线程去定时清理
      notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
      return false;
    }
  }

  public void evictAll() {
    List<RealConnection> evictedConnections = new ArrayList<>();
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        if (connection.transmitters.isEmpty()) {
          connection.noNewExchanges = true;
          evictedConnections.add(connection);
          i.remove();
        }
      }
    }

    for (RealConnection connection : evictedConnections) {
      closeQuietly(connection.socket());
    }
  }

  /**
   * 移除闲置时间最长的，长到已经超出闲置时间限制的 connection
   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
   * either it has exceeded the keep alive limit or the idle connections limit.
   *	返回当前 下次调度该方法的 纳秒，返回-1则是不需要清楚了
   * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
   * -1 if no further cleanups are required.
   */
  long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;
    //找出符合条件的 connection，或者返回下一次到期的时间
    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use, keep searching.//大于0说明connection有引用，跳过此次循环
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        //执行到这里说明 引用为0，闲置connection要添加计数
        idleConnectionCount++;
        //之前有定义  released.idleAtNanos = System.nanoTime();	
        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;//当前时间 - 标记释放的时间戳；等于闲置时间
        if (idleDurationNs > longestIdleDurationNs) {//通过此判断找到闲置时间最久的connection
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
    //闲置时间最久，且超出保持存活的时间或者 超出了维护闲置connection的数量最大值，就移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {//闲置的计数不为0，则返回下次要移除的时间
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {//使用中connection数目不为0，则返回默认闲置时间，到时再次运行一次
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
        return keepAliveDurationNs;
      } else {//没有是使用的或者闲置的
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }
    //关闭socket，符合条件1的则关闭当前的连接	
    closeQuietly(longestIdleConnection.socket());
    //通知立刻执行清理
    // Cleanup again immediately.
    return 0;
  }

  /**
   * 移除遗漏的发射器，并且返回剩下存活的发射器
   * Prunes any leaked transmitters and then returns the number of remaining live transmitters on
   * {@code connection}. Transmitters are leaked if the connection is tracking them but the
   * application code has abandoned them. Leak detection is imprecise and relies on garbage
   * collection.
   *如果连接还在追踪，但是应用已经忽略这些连接， 这些连接是遗漏的 ；遗漏是在垃圾回收 不精准并且可以信赖的
   */
  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<Transmitter>> references = connection.transmitters;
    for (int i = 0; i < references.size(); ) {
    	//遍历当前的connection的发射器引用列表
      Reference<Transmitter> reference = references.get(i);
      //引用不为空，表示当前的connection还在使用，跳过当前的，遍历下一个
      if (reference.get() != null) {
        i++;
        continue;
      }
      //当发现这种Reference的引用的transmitter已经为空了，说明应用调用方发生了bug，提示用户
      // We've discovered a leaked transmitter. This is an application bug.
      TransmitterReference transmitterRef = (TransmitterReference) reference;
      String message = "A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?";
      Platform.get().logCloseableLeak(message, transmitterRef.callStackTrace);
      //移除对应的Reference	
      references.remove(i);
      //将connection的状态重置为没有新的数据交互，即可复用的初始状态
      connection.noNewExchanges = true;
      //当本身为空，或移除到只剩下空时，返回0，并且利用当前时间 减去 设定的最大存活时间，得到闲置的时间
      // If this was the last allocation, the connection is eligible for immediate eviction.
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }

    return references.size();
  }

  /** Track a bad route in the route database. Other routes will be attempted first. */
  public void connectFailed(Route failedRoute, IOException failure) {
    // Tell the proxy selector when we fail to connect on a fresh connection.
    if (failedRoute.proxy().type() != Proxy.Type.DIRECT) {
      Address address = failedRoute.address();
      address.proxySelector().connectFailed(
          address.url().uri(), failedRoute.proxy().address(), failure);
    }

    routeDatabase.failed(failedRoute);
  }
}
